// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AutoScale.cs" company="">
//   
// </copyright>
// <summary>
//   Class responsible for creating nicely looking scales
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Graphix.Helpers
{
    using System;

    /// <summary>
    /// Class responsible for creating nicely looking scales
    /// </summary>
    public class AutoScale
    {
        #region Fields
        /// <summary>
        /// Information if scale nicely or not.
        /// </summary>
        private readonly bool scaleNicely;

        /// <summary>
        /// The provided maximum point.
        /// </summary>
        private float maxPoint;

        /// <summary>
        /// The provided minimum point.
        /// </summary>
        private float minPoint;

        /// <summary>
        /// The maximum number of ticks.
        /// </summary>
        private float maxTicks = 5;
        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoScale"/> class. 
        /// </summary>
        /// <param name="min">
        /// The minimum data point on the axis.
        /// </param>
        /// <param name="max">
        /// The maximum data point on the axis.
        /// </param>
        /// <param name="scaleNicely">
        /// The scale Nicely.
        /// </param>
        public AutoScale(float min, float max, bool scaleNicely = true, int maxTicks = 5)
        {
            this.maxTicks = maxTicks;
            this.scaleNicely = scaleNicely;
            if (min.Equals(max))
            {
                if (min.Equals(float.NaN)
                    || min.Equals(0))
                {
                    this.minPoint = -1;
                    this.maxPoint = 1;
                }
                else
                {
                    if (min.Equals(0))
                    {
                        if (min > 0)
                        {
                            min = -min;
                        }

                        this.minPoint = min;
                        this.maxPoint = -min;
                    }
                    else
                    {
                        if (max > 0)
                        {
                            max = -max;
                        }

                        this.minPoint = max;
                        this.maxPoint = -max;
                    }
                }
            }
            else
            {
                this.minPoint = min;
                this.maxPoint = max;
            }

            this.Calculate();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the maximum number of ticks.
        /// </summary>
        public float MaxTicks
        {
            get
            {
                return this.maxTicks;
            }

            set
            {
                this.maxTicks = value;
                this.Calculate();
            }
        }

        /// <summary>
        /// Gets the nice maximum.
        /// </summary>
        public float NiceMax { get; private set; }

        /// <summary>
        /// Gets the nice minimum.
        /// </summary>
        public float NiceMin { get; private set; }

        /// <summary>
        /// Gets the range.
        /// </summary>
        public float Range { get; private set; }

        /// <summary>
        /// Gets the tick spacing.
        /// </summary>
        public float TickSpacing { get; private set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Setup minimum and maximum point.
        /// </summary>
        /// <param name="min">
        /// Minimum point.
        /// </param>
        /// <param name="max">
        /// Maximum point.
        /// </param>
        public void SetMinMaxPoints(float min, float max)
        {
            this.minPoint = min;
            this.maxPoint = max;
            this.Calculate();
        }

        #endregion

        #region Methods
        /// <summary>
        /// Approximates number to "nice" one.
        /// </summary>
        /// <param name="range">
        /// Approximated number.
        /// </param>
        /// <returns>
        /// result of approximation.
        /// </returns>
        private float ApproxNiceNumber(double range)
        {
            double roundedFraction;
            double log10Range = Math.Log(range) / Math.Log(10);
            double exponent = Math.Floor(log10Range);
            double fraction = range / Math.Pow(10, exponent);

            double mod = fraction % 0.5;
            if (mod != 0)
            {
                roundedFraction = fraction - mod;
                roundedFraction += 0.5;
            }
            else
            {
                roundedFraction = fraction;
            }

            return (float)(roundedFraction * Math.Pow(10, exponent));
        }

        /// <summary>
        /// Calculate values for NiceMin, NiceMax, Range and TickSpacing.
        /// </summary>
        private void Calculate()
        {
            if (this.scaleNicely)
            {
                this.CalculateNicely();
            }
            else
            {
                this.CalculateUgly();
            }

            this.Range = Math.Abs(this.NiceMax - this.NiceMin);
        }

        /// <summary>
        /// Calculate values with nice scale.
        /// </summary>
        private void CalculateNicely()
        {
            this.Range = this.ApproxNiceNumber(this.maxPoint - this.minPoint);
            this.TickSpacing = this.ApproxNiceNumber(this.Range / (this.maxTicks - 1));
            this.NiceMin = (float)Math.Floor(this.minPoint / this.TickSpacing) * this.TickSpacing;
            this.NiceMax = (float)Math.Ceiling(this.maxPoint / this.TickSpacing) * this.TickSpacing;
        }

        /// <summary>
        /// Calculate values without nice scale.
        /// </summary>
        private void CalculateUgly()
        {
            this.Range = this.maxPoint - this.minPoint;
            this.TickSpacing = this.Range / (this.maxTicks - 1);
            this.NiceMin = this.minPoint;
            this.NiceMax = this.maxPoint;
        }
        #endregion
    }
}